/*
 ===========================================================================
 Copyright (C) 1999-2005 Id Software, Inc.
 Copyright (C) 2000-2006 Tim Angus

 This file is part of Tremulous.

 Tremulous is free software; you can redistribute it
 and/or modify it under the terms of the GNU General Public License as
 published by the Free Software Foundation; either version 2 of the License,
 or (at your option) any later version.

 Tremulous is distributed in the hope that it will be
 useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 GNU General Public License for more details.

 You should have received a copy of the GNU General Public License
 along with Tremulous; if not, write to the Free Software
 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 ===========================================================================
 */

// cg_ents.c -- present snapshot entities, happens every single frame


#include "cg_local.h"

/*
 ======================
 CG_DrawBoxFace

 Draws a bounding box face
 ======================
 */
static void
CG_DrawBoxFace(vec3_t a, vec3_t b, vec3_t c, vec3_t d)
{
  polyVert_t verts[4];
  vec4_t color =
  { 255.0f, 0.0f, 0.0f, 128.0f };

  VectorCopy( d, verts[ 0 ].xyz );
  verts[0].st[0] = 1;
  verts[0].st[1] = 1;
  Vector4Copy( color, verts[ 0 ].modulate );

  VectorCopy( c, verts[ 1 ].xyz );
  verts[1].st[0] = 1;
  verts[1].st[1] = 0;
  Vector4Copy( color, verts[ 1 ].modulate );

  VectorCopy( b, verts[ 2 ].xyz );
  verts[2].st[0] = 0;
  verts[2].st[1] = 0;
  Vector4Copy( color, verts[ 2 ].modulate );

  VectorCopy( a, verts[ 3 ].xyz );
  verts[3].st[0] = 0;
  verts[3].st[1] = 1;
  Vector4Copy( color, verts[ 3 ].modulate );

  trap_R_AddPolyToScene(cgs.media.outlineShader, 4, verts);
}

/*
 ======================
 CG_DrawBoundingBox

 Draws a bounding box
 ======================
 */
void
CG_DrawBoundingBox(vec3_t origin, vec3_t mins, vec3_t maxs)
{
  vec3_t ppp, mpp, mmp, pmp;
  vec3_t mmm, pmm, ppm, mpm;

  ppp[0] = origin[0] + maxs[0];
  ppp[1] = origin[1] + maxs[1];
  ppp[2] = origin[2] + maxs[2];

  mpp[0] = origin[0] + mins[0];
  mpp[1] = origin[1] + maxs[1];
  mpp[2] = origin[2] + maxs[2];

  mmp[0] = origin[0] + mins[0];
  mmp[1] = origin[1] + mins[1];
  mmp[2] = origin[2] + maxs[2];

  pmp[0] = origin[0] + maxs[0];
  pmp[1] = origin[1] + mins[1];
  pmp[2] = origin[2] + maxs[2];

  ppm[0] = origin[0] + maxs[0];
  ppm[1] = origin[1] + maxs[1];
  ppm[2] = origin[2] + mins[2];

  mpm[0] = origin[0] + mins[0];
  mpm[1] = origin[1] + maxs[1];
  mpm[2] = origin[2] + mins[2];

  mmm[0] = origin[0] + mins[0];
  mmm[1] = origin[1] + mins[1];
  mmm[2] = origin[2] + mins[2];

  pmm[0] = origin[0] + maxs[0];
  pmm[1] = origin[1] + mins[1];
  pmm[2] = origin[2] + mins[2];

  //phew!

  CG_DrawBoxFace(ppp, mpp, mmp, pmp);
  CG_DrawBoxFace(ppp, pmp, pmm, ppm);
  CG_DrawBoxFace(mpp, ppp, ppm, mpm);
  CG_DrawBoxFace(mmp, mpp, mpm, mmm);
  CG_DrawBoxFace(pmp, mmp, mmm, pmm);
  CG_DrawBoxFace(mmm, mpm, ppm, pmm);
}

/*
 ======================
 CG_PositionEntityOnTag

 Modifies the entities position and axis by the given
 tag location
 ======================
 */
void
CG_PositionEntityOnTag(refEntity_t *entity, const refEntity_t *parent, qhandle_t parentModel, char *tagName)
{
  int i;
  orientation_t lerped;

  // lerp the tag
  trap_R_LerpTag(&lerped, parentModel, parent->oldframe, parent->frame, 1.0 - parent->backlerp, tagName);

  // FIXME: allow origin offsets along tag?
  VectorCopy( parent->origin, entity->origin );
  for(i = 0;i < 3;i++)
    VectorMA( entity->origin, lerped.origin[ i ], parent->axis[ i ], entity->origin );

  // had to cast away the const to avoid compiler problems...
  MatrixMultiply(lerped.axis, ((refEntity_t *) parent)->axis, entity->axis);
  entity->backlerp = parent->backlerp;
}

/*
 ======================
 CG_PositionRotatedEntityOnTag

 Modifies the entities position and axis by the given
 tag location
 ======================
 */
void
CG_PositionRotatedEntityOnTag(refEntity_t *entity, const refEntity_t *parent, qhandle_t parentModel, char *tagName)
{
  int i;
  orientation_t lerped;
  vec3_t tempAxis[3];

  //AxisClear( entity->axis );
  // lerp the tag
  trap_R_LerpTag(&lerped, parentModel, parent->oldframe, parent->frame, 1.0 - parent->backlerp, tagName);

  // FIXME: allow origin offsets along tag?
  VectorCopy( parent->origin, entity->origin );
  for(i = 0;i < 3;i++)
    VectorMA( entity->origin, lerped.origin[ i ], parent->axis[ i ], entity->origin );

  // had to cast away the const to avoid compiler problems...
  MatrixMultiply(entity->axis, lerped.axis, tempAxis);
  MatrixMultiply(tempAxis, ((refEntity_t *) parent)->axis, entity->axis);
}

/*
 ==========================================================================

 FUNCTIONS CALLED EACH FRAME

 ==========================================================================
 */

/*
 ======================
 CG_SetEntitySoundPosition

 Also called by event processing code
 ======================
 */
void
CG_SetEntitySoundPosition(centity_t *cent)
{
  if (cent->currentState.solid == SOLID_BMODEL)
  {
    vec3_t origin;
    float *v;

    v = cgs.inlineModelMidpoints[cent->currentState.modelindex];
    VectorAdd( cent->lerpOrigin, v, origin );
    trap_S_UpdateEntityPosition(cent->currentState.number, origin);
  }
  else
    trap_S_UpdateEntityPosition(cent->currentState.number, cent->lerpOrigin);
}

/*
 ==================
 CG_EntityEffects

 Add continuous entity effects, like local entity emission and lighting
 ==================
 */
static void
CG_EntityEffects(centity_t *cent)
{
  // update sound origins
  CG_SetEntitySoundPosition(cent);

  // add loop sound
  if (cent->currentState.loopSound)
  {
    if (cent->currentState.eType != ET_SPEAKER)
    {
      trap_S_AddLoopingSound(cent->currentState.number, cent->lerpOrigin, vec3_origin, cgs.gameSounds[cent->currentState.loopSound]);
    }
    else
    {
      trap_S_AddRealLoopingSound(cent->currentState.number, cent->lerpOrigin, vec3_origin, cgs.gameSounds[cent->currentState.loopSound]);
    }
  }

  // constant light glow
  if (cent->currentState.constantLight)
  {
    int cl;
    int i, r, g, b;

    cl = cent->currentState.constantLight;
    r = cl & 255;
    g = (cl >> 8) & 255;
    b = (cl >> 16) & 255;
    i = ((cl >> 24) & 255) * 4;
    trap_R_AddLightToScene(cent->lerpOrigin, i, r, g, b);
  }

  if (CG_IsTrailSystemValid(&cent->muzzleTS))
  {
    if (cent->currentState.eType == ET_BUILDABLE)
    {
      vec3_t front, back;

      CG_AttachmentPoint(&cent->muzzleTS->frontAttachment, front);
      CG_AttachmentPoint(&cent->muzzleTS->backAttachment, back);

      if (Distance(front, back) > TESLAGEN_RANGE)
        CG_DestroyTrailSystem(&cent->muzzleTS);
    }

    if (cg.time > cent->muzzleTSDeathTime && CG_IsTrailSystemValid(&cent->muzzleTS))
      CG_DestroyTrailSystem(&cent->muzzleTS);
  }
}

/*
 ==================
 CG_General
 ==================
 */
static void
CG_General(centity_t *cent)
{
  refEntity_t ent;
  entityState_t *s1;

  s1 = &cent->currentState;

  // if set to invisible, skip
  if (!s1->modelindex)
    return;

  memset(&ent, 0, sizeof(ent));

  // set frame

  ent.frame = s1->frame;
  ent.oldframe = ent.frame;
  ent.backlerp = 0;

  VectorCopy( cent->lerpOrigin, ent.origin);
  VectorCopy( cent->lerpOrigin, ent.oldorigin);

  ent.hModel = cgs.gameModels[s1->modelindex];

  // player model
  if (s1->number == cg.snap->ps.clientNum)
    ent.renderfx |= RF_THIRD_PERSON; // only draw from mirrors

  // convert angles to axis
  AnglesToAxis(cent->lerpAngles, ent.axis);

  // add to refresh list
  trap_R_AddRefEntityToScene(&ent);
}

/*
 ==================
 CG_Speaker

 Speaker entities can automatically play sounds
 ==================
 */
static void
CG_Speaker(centity_t *cent)
{
  if (!cent->currentState.clientNum)
  { // FIXME: use something other than clientNum...
    return; // not auto triggering
  }

  if (cg.time < cent->miscTime)
    return;

  trap_S_StartSound(NULL, cent->currentState.number, CHAN_ITEM, cgs.gameSounds[cent->currentState.eventParm]);

  //  ent->s.frame = ent->wait * 10;
  //  ent->s.clientNum = ent->random * 10;
  cent->miscTime = cg.time + cent->currentState.frame * 100 + cent->currentState.clientNum * 100 * crandom( );
}

//============================================================================


/*
 ==================
 CG_AI_Node
 ==================
 */
static void
CG_AI_Node(centity_t * cent)
{
  refEntity_t ent;
  vec3_t origin, delta, dir, vec, up =
  { 0, 0, 1 };
  float len;
  int i, node, digits[10], numdigits, negative;
  int numberSize = 8;

  enum
  {
    NODE_MOVE, NODE_PLATFORM, NODE_TRIGGER_TELEPORT,
    //  NODE_TARGET_TELEPORT,
    NODE_ITEM,
    NODE_WATER,
    NODE_GRAPPLE,
    NODE_JUMP,
    NODE_DUCKJUMP,
    NODE_JUMPPAD,
    NODE_LADDER,
    NODE_ALL = 99,
  // For selecting all nodes
  };

  entityState_t *s1;

  s1 = &cent->currentState;

  memset(&ent, 0, sizeof(ent));

#if 0

  // set frame
  VectorCopy(cent->lerpOrigin, ent.origin);
  VectorCopy(cent->lerpOrigin, ent.oldorigin);

  // convert angles to axis
  AnglesToAxis(cent->lerpAngles, ent.axis);

  // add to refresh list
  trap_R_AddRefEntityToScene(&ent);

#else
  // draw node number as sprite
  // code based on CG_AddScorePlum

  ent.reType = RT_SPRITE;
  ent.radius = 5;

  ent.shaderRGBA[0] = 0xff;//0xff;
  ent.shaderRGBA[1] = 0xff;
  ent.shaderRGBA[2] = 0xff;
  ent.shaderRGBA[3] = 0xff;

  switch(s1->constantLight)
  {
    case NODE_MOVE:
      ent.shaderRGBA[0] = 0;
      break;
    case NODE_JUMP:
      ent.shaderRGBA[1] = 0;
      break;
    case NODE_LADDER:
      ent.shaderRGBA[2] = 0;
      break;
    default:

      break;
  }

//  ent.shaderRGBA[0] = s1->constantLight;//0xff;
//  ent.shaderRGBA[1] = 0xff;
//  ent.shaderRGBA[2] = 0xff;
//  ent.shaderRGBA[3] = 0xff;

  VectorCopy(cent->lerpOrigin, origin);
  origin[2] += 5;

  VectorSubtract(cg.refdef.vieworg, origin, dir);
  CrossProduct(dir, up, vec);
  VectorNormalize(vec);
  //VectorMA(origin, -10 + 20 * sin(c * 2 * M_PI), vec, origin);

  // if the view would be "inside" the sprite, kill the sprite
  // so it doesn't add too much overdraw
  VectorSubtract(origin, cg.refdef.vieworg, delta);
  len = VectorLength(delta);
  if (len < 20)
  {
    return;
  }

  node = s1->otherEntityNum;

  negative = qfalse;
  if (node < 0)
  {
    negative = qtrue;
    node = -node;
  }

  for(numdigits = 0;!(numdigits && !node);numdigits++)
  {
    digits[numdigits] = node % 10;
    node = node / 10;
  }

  if (negative)
  {
    digits[numdigits] = 10;
    numdigits++;
  }

  for(i = 0;i < numdigits;i++)
  {
    VectorMA(origin, (float)(((float)numdigits / 2) - i) * numberSize, vec, ent.origin);
    ent.customShader = cgs.media.numberShaders[digits[numdigits - 1 - i]];
    trap_R_AddRefEntityToScene(&ent);
  }
#endif
}

/*
 ===============
 CG_AI_Link
 ===============
 */
static void
CG_AI_Link(centity_t * cent)
{
  refEntity_t beam;
  entityState_t *s1;

  s1 = &cent->currentState;

  memset(&beam, 0, sizeof(beam));

  VectorCopy(s1->pos.trBase, beam.origin);
  VectorCopy(s1->origin2, beam.oldorigin);

  //beam.reType = RT_BEAM;
  //This may not work.
  beam.reType = RT_LIGHTNING;
  beam.customShader = cgs.media.balloonShader; //If dont draw try others..
  trap_R_AddRefEntityToScene(&beam);
}

/*
 ===============
 CG_AI_Link
 ===============
 */
static void
CG_Laser(centity_t * cent)
{
  refEntity_t beam;
  entityState_t *s1;
  vec3_t  origin, forward, up;


  s1 = &cent->currentState;

  memset(&beam, 0, sizeof(beam));

  cent->trailTime = cg.time;

  BG_EvaluateTrajectory( &s1->pos, cg.time, origin );

  VectorCopy ( cg_entities[ cent->currentState.otherEntityNum ].lerpOrigin, beam.origin );
  beam.origin[2] += 26;
  AngleVectors( cg_entities[ cent->currentState.otherEntityNum ].lerpAngles, forward, NULL, up );
  VectorMA( beam.origin, -6, up, beam.origin );
  VectorCopy( origin, beam.oldorigin );


  VectorCopy(s1->pos.trBase, beam.origin);
  VectorCopy(s1->origin2, beam.oldorigin);

  beam.reType = RT_LIGHTNING;
  beam.customShader = cgs.media.laser; //If dont draw try others..
  trap_R_AddRefEntityToScene(&beam);
}

/*
 ===============
 CG_LaunchMissile
 ===============
 */
static void
CG_LaunchMissile(centity_t *cent)
{
  entityState_t *es;
  const weaponInfo_t *wi;
  particleSystem_t *ps;
  trailSystem_t *ts;
  weapon_t weapon;
  weaponMode_t weaponMode;

  es = &cent->currentState;

  weapon = es->weapon;
  if (weapon > WP_NUM_WEAPONS)
    weapon = WP_NONE;

  wi = &cg_weapons[weapon];
  weaponMode = es->generic1;

  if (wi->wim[weaponMode].missileParticleSystem)
  {
    ps = CG_SpawnNewParticleSystem(wi->wim[weaponMode].missileParticleSystem);

    if (CG_IsParticleSystemValid(&ps))
    {
      CG_SetAttachmentCent(&ps->attachment, cent);
      CG_AttachToCent(&ps->attachment);
    }
  }

  if (wi->wim[weaponMode].missileTrailSystem)
  {
    ts = CG_SpawnNewTrailSystem(wi->wim[weaponMode].missileTrailSystem);

    if (CG_IsTrailSystemValid(&ts))
    {
      CG_SetAttachmentCent(&ts->frontAttachment, cent);
      CG_AttachToCent(&ts->frontAttachment);
    }
  }
}

/*
 ===============
 CG_Missile
 ===============
 */
static void
CG_Missile(centity_t *cent)
{
  refEntity_t ent;
  entityState_t *es;
  const weaponInfo_t *wi;
  weapon_t weapon;
  weaponMode_t weaponMode;
  const weaponInfoMode_t *wim;
  vec3_t tempAngles;

  es = &cent->currentState;

  weapon = es->weapon;
  if (weapon > WP_NUM_WEAPONS)
    weapon = WP_NONE;

  wi = &cg_weapons[weapon];
  weaponMode = es->generic1;

  wim = &wi->wim[weaponMode];

  // calculate the axis
  VectorCopy( es->angles, cent->lerpAngles );

  // add dynamic light
  if (wim->missileDlight)
  {
    trap_R_AddLightToScene(cent->lerpOrigin, wim->missileDlight, wim->missileDlightColor[0], wim->missileDlightColor[1], wim->missileDlightColor[2]);
  }

  // add missile sound
  if (wim->missileSound)
  {
    vec3_t velocity;

    BG_EvaluateTrajectoryDelta(&cent->currentState.pos, cg.time, velocity);

    trap_S_AddLoopingSound(cent->currentState.number, cent->lerpOrigin, velocity, wim->missileSound);
  }

  // create the render entity
  memset(&ent, 0, sizeof(ent));
  VectorCopy( cent->lerpOrigin, ent.origin );
  VectorCopy( cent->lerpOrigin, ent.oldorigin );

  if (wim->usesSpriteMissle)
  {
    ent.reType = RT_SPRITE;
    ent.radius = wim->missileSpriteSize;
    ent.rotation = 0;
    ent.customShader = wim->missileSprite;
    ent.shaderRGBA[0] = 0xFF;
    ent.shaderRGBA[1] = 0xFF;
    ent.shaderRGBA[2] = 0xFF;
    ent.shaderRGBA[3] = 0xFF;
  }
  else
  {
    ent.hModel = wim->missileModel;
    ent.renderfx = wim->missileRenderfx | RF_NOSHADOW;

    // convert direction of travel into axis
    if (VectorNormalize2(es->pos.trDelta, ent.axis[0]) == 0)
      ent.axis[0][2] = 1;

    // spin as it moves
    if(weapon == WP_MINE)
    {
      AnglesToAxis(cent->lerpAngles, ent.axis);
    }
    else if(weapon == WP_AXE)
    {
      RotateAroundDirection(ent.axis, -90);
      //VectorClear(tempAngles);
      //AnglesToAxis(tempAngles, ent.axis);
      //RotateAroundAxe(ent.axis, cg.time / 4);
    }
    else if (es->pos.trType != TR_STATIONARY)
      RotateAroundDirection(ent.axis, cg.time / 4);
    else
      RotateAroundDirection(ent.axis, es->time);


    if (wim->missileAnimates)
    {
      int timeSinceStart = cg.time - es->time;

      if (wim->missileAnimLooping)
      {
        ent.frame = wim->missileAnimStartFrame + (int) ((timeSinceStart / 1000.0f) * wim->missileAnimFrameRate) % wim->missileAnimNumFrames;
      }
      else
      {
        ent.frame = wim->missileAnimStartFrame + (int) ((timeSinceStart / 1000.0f) * wim->missileAnimFrameRate);

        if (ent.frame > (wim->missileAnimStartFrame + wim->missileAnimNumFrames))
          ent.frame = wim->missileAnimStartFrame + wim->missileAnimNumFrames;
      }
    }
  }

  //only refresh if there is something to display
  if (wim->missileSprite || wim->missileModel)
    trap_R_AddRefEntityToScene(&ent);
}

/*
 ===============
 CG_Mover
 ===============
 */
static void
CG_Mover(centity_t *cent)
{
  refEntity_t ent;
  entityState_t *s1;

  s1 = &cent->currentState;

  // create the render entity
  memset(&ent, 0, sizeof(ent));
  VectorCopy( cent->lerpOrigin, ent.origin );
  VectorCopy( cent->lerpOrigin, ent.oldorigin );
  AnglesToAxis(cent->lerpAngles, ent.axis);

  ent.renderfx = RF_NOSHADOW;

  // flicker between two skins (FIXME?)
  ent.skinNum = (cg.time >> 6) & 1;

  // get the model, either as a bmodel or a modelindex
  if (s1->solid == SOLID_BMODEL)
    ent.hModel = cgs.inlineDrawModel[s1->modelindex];
  else
    ent.hModel = cgs.gameModels[s1->modelindex];

  // add to refresh list
  trap_R_AddRefEntityToScene(&ent);

  // add the secondary model
  if (s1->modelindex2)
  {
    ent.skinNum = 0;
    ent.hModel = cgs.gameModels[s1->modelindex2];
    trap_R_AddRefEntityToScene(&ent);
  }

}

/*
 ===============
 CG_Beam

 Also called as an event
 ===============
 */
void
CG_Beam(centity_t *cent)
{
  refEntity_t ent;
  entityState_t *s1;

  s1 = &cent->currentState;

  // create the render entity
  memset(&ent, 0, sizeof(ent));
  VectorCopy( s1->pos.trBase, ent.origin );
  VectorCopy( s1->origin2, ent.oldorigin );
  AxisClear(ent.axis);
  ent.reType = RT_BEAM;

  ent.renderfx = RF_NOSHADOW;

  // add to refresh list
  trap_R_AddRefEntityToScene(&ent);
}

/*
 ===============
 CG_Portal
 ===============
 */
static void
CG_Portal(centity_t *cent)
{
  refEntity_t ent;
  entityState_t *s1;

  s1 = &cent->currentState;

  // create the render entity
  memset(&ent, 0, sizeof(ent));
  VectorCopy( cent->lerpOrigin, ent.origin );
  VectorCopy( s1->origin2, ent.oldorigin );
  ByteToDir(s1->eventParm, ent.axis[0]);
  PerpendicularVector(ent.axis[1], ent.axis[0]);

  // negating this tends to get the directions like they want
  // we really should have a camera roll value
  VectorSubtract( vec3_origin, ent.axis[ 1 ], ent.axis[ 1 ] );

  CrossProduct(ent.axis[0], ent.axis[1], ent.axis[2]);
  ent.reType = RT_PORTALSURFACE;
  ent.oldframe = s1->powerups;
  ent.frame = s1->frame; // rotation speed
  ent.skinNum = s1->clientNum / 256.0 * 360; // roll offset

  // add to refresh list
  trap_R_AddRefEntityToScene(&ent);
}

//============================================================================

#define SETBOUNDS(v1,v2,r)  ((v1)[0]=(-r/2),(v1)[1]=(-r/2),(v1)[2]=(-r/2),\
                             (v2)[0]=(r/2),(v2)[1]=(r/2),(v2)[2]=(r/2))
#define RADIUSSTEP          0.5f

#define FLARE_OFF       0
#define FLARE_NOFADE    1
#define FLARE_TIMEFADE  2
#define FLARE_REALFADE  3

/*
 =========================
 CG_LightFlare
 =========================
 */
static void
CG_LightFlare(centity_t *cent)
{
  refEntity_t flare;
  entityState_t *es;
  vec3_t forward, delta;
  float len;
  trace_t tr;
  float maxAngle;
  vec3_t mins, maxs, start, end;
  float srcRadius, srLocal, ratio = 1.0f;
  int entityNum;

  es = &cent->currentState;

  if (cg.renderingThirdPerson)
    entityNum = MAGIC_TRACE_HACK;
  else
    entityNum = cg.predictedPlayerState.clientNum;

  //don't draw light flares
  if (cg_lightFlare.integer == FLARE_OFF)
    return;

  //flare is "off"
  if (es->eFlags & EF_NODRAW)
    return;

  CG_Trace(&tr, cg.refdef.vieworg, NULL, NULL, es->angles2, entityNum, MASK_SHOT);

  //if there is no los between the view and the flare source
  //it definately cannot be seen
  if (tr.fraction < 1.0f || tr.allsolid)
    return;

  memset(&flare, 0, sizeof(flare));

  flare.reType = RT_SPRITE;
  flare.customShader = cgs.gameShaders[es->modelindex];
  flare.shaderRGBA[0] = 0xFF;
  flare.shaderRGBA[1] = 0xFF;
  flare.shaderRGBA[2] = 0xFF;
  flare.shaderRGBA[3] = 0xFF;

  //flares always drawn before the rest of the scene
  flare.renderfx |= RF_DEPTHHACK;

  //bunch of geometry
  AngleVectors(es->angles, forward, NULL, NULL);
  VectorCopy( cent->lerpOrigin, flare.origin );
  VectorSubtract( flare.origin, cg.refdef.vieworg, delta );
  len = VectorLength(delta);
  VectorNormalize(delta);

  //flare is too close to camera to be drawn
  if (len < es->generic1)
    return;

  //don't bother for flares behind the view plane
  if (DotProduct( delta, cg.refdef.viewaxis[ 0 ] ) < 0.0)
    return;

  //only recalculate radius and ratio every three frames
  if (!(cg.clientFrame % 2))
  {
    //can only see the flare when in front of it
    flare.radius = len / es->origin2[0];

    if (es->origin2[2] == 0)
      srcRadius = srLocal = flare.radius / 2.0f;
    else
      srcRadius = srLocal = len / es->origin2[2];

    maxAngle = es->origin2[1];

    if (maxAngle > 0.0f)
    {
      float radiusMod = 1.0f - (180.0f - RAD2DEG(
          acos( DotProduct( delta, forward ) ) )) / maxAngle;

      if (radiusMod < 0.0f)
        radiusMod = 0.0f;

      flare.radius *= radiusMod;
    }

    if (flare.radius < 0.0f)
      flare.radius = 0.0f;

    VectorMA( flare.origin, -flare.radius, delta, end );
    VectorMA( cg.refdef.vieworg, flare.radius, delta, start );

    if (cg_lightFlare.integer == FLARE_REALFADE)
    {
      //"correct" flares
      CG_BiSphereTrace(&tr, cg.refdef.vieworg, end, 1.0f, srcRadius, entityNum, MASK_SHOT);

      if (tr.fraction < 1.0f)
        ratio = tr.lateralFraction;
      else
        ratio = 1.0f;
    }
    else if (cg_lightFlare.integer == FLARE_TIMEFADE)
    {
      //draw timed flares
      SETBOUNDS( mins, maxs, srcRadius );
      CG_Trace(&tr, start, mins, maxs, end, entityNum, MASK_SHOT);

      if ((tr.fraction < 1.0f || tr.startsolid) && cent->lfs.status)
      {
        cent->lfs.status = qfalse;
        cent->lfs.lastTime = cg.time;
      }
      else if ((tr.fraction == 1.0f && !tr.startsolid) && !cent->lfs.status)
      {
        cent->lfs.status = qtrue;
        cent->lfs.lastTime = cg.time;
      }

      //fade flare up
      if (cent->lfs.status)
      {
        if (cent->lfs.lastTime + es->time > cg.time)
          ratio = (float) (cg.time - cent->lfs.lastTime) / es->time;
      }

      //fade flare down
      if (!cent->lfs.status)
      {
        if (cent->lfs.lastTime + es->time > cg.time)
        {
          ratio = (float) (cg.time - cent->lfs.lastTime) / es->time;
          ratio = 1.0f - ratio;
        }
        else
          ratio = 0.0f;
      }
    }
    else if (cg_lightFlare.integer == FLARE_NOFADE)
    {
      //draw nofade flares
      SETBOUNDS( mins, maxs, srcRadius );
      CG_Trace(&tr, start, mins, maxs, end, entityNum, MASK_SHOT);

      //flare source occluded
      if ((tr.fraction < 1.0f || tr.startsolid))
        ratio = 0.0f;
    }
  }
  else
  {
    ratio = cent->lfs.lastRatio;
    flare.radius = cent->lfs.lastRadius;
  }

  cent->lfs.lastRatio = ratio;
  cent->lfs.lastRadius = flare.radius;

  if (ratio < 1.0f)
  {
    flare.radius *= ratio;
    flare.shaderRGBA[3] = (byte) ((float) flare.shaderRGBA[3] * ratio);
  }

  if (flare.radius <= 0.0f)
    return;

  trap_R_AddRefEntityToScene(&flare);
}

/*
 =========================
 CG_Lev2ZapChain
 =========================
 */
static void
CG_Lev2ZapChain(centity_t *cent)
{
  int i;
  entityState_t *es;
  centity_t *source = NULL, *target = NULL;

  es = &cent->currentState;

  for(i = 0;i <= 2;i++)
  {
    switch(i)
    {
      case 0:
        if (es->time <= 0)
          continue;

        source = &cg_entities[es->powerups];
        target = &cg_entities[es->time];
        break;

      case 1:
        if (es->time2 <= 0)
          continue;

        source = &cg_entities[es->time];
        target = &cg_entities[es->time2];
        break;

      case 2:
        if (es->constantLight <= 0)
          continue;

        source = &cg_entities[es->time2];
        target = &cg_entities[es->constantLight];
        break;
    }

    if (!CG_IsTrailSystemValid(&cent->level2ZapTS[i]))
      cent->level2ZapTS[i] = CG_SpawnNewTrailSystem(cgs.media.level2ZapTS);

    if (CG_IsTrailSystemValid(&cent->level2ZapTS[i]))
    {
      CG_SetAttachmentCent(&cent->level2ZapTS[i]->frontAttachment, source);
      CG_SetAttachmentCent(&cent->level2ZapTS[i]->backAttachment, target);
      CG_AttachToCent(&cent->level2ZapTS[i]->frontAttachment);
      CG_AttachToCent(&cent->level2ZapTS[i]->backAttachment);
    }
  }
}

/*
 =========================
 CG_AdjustPositionForMover

 Also called by client movement prediction code
 =========================
 */
void
CG_AdjustPositionForMover(const vec3_t in, int moverNum, int fromTime, int toTime, vec3_t out)
{
  centity_t *cent;
  vec3_t oldOrigin, origin, deltaOrigin;
  vec3_t oldAngles, angles, deltaAngles;

  if (moverNum <= 0 || moverNum >= ENTITYNUM_MAX_NORMAL)
  {
    VectorCopy( in, out );
    return;
  }

  cent = &cg_entities[moverNum];

  if (cent->currentState.eType != ET_MOVER)
  {
    VectorCopy( in, out );
    return;
  }

  BG_EvaluateTrajectory(&cent->currentState.pos, fromTime, oldOrigin);
  BG_EvaluateTrajectory(&cent->currentState.apos, fromTime, oldAngles);

  BG_EvaluateTrajectory(&cent->currentState.pos, toTime, origin);
  BG_EvaluateTrajectory(&cent->currentState.apos, toTime, angles);

  VectorSubtract( origin, oldOrigin, deltaOrigin );
  VectorSubtract( angles, oldAngles, deltaAngles );

  VectorAdd( in, deltaOrigin, out );

  // FIXME: origin change when on a rotating object
}

/*
 =============================
 CG_InterpolateEntityPosition
 =============================
 */
static void
CG_InterpolateEntityPosition(centity_t *cent)
{
  vec3_t current, next;
  float f;

  // it would be an internal error to find an entity that interpolates without
  // a snapshot ahead of the current one
  if (cg.nextSnap == NULL)
    CG_Error("CG_InterpoateEntityPosition: cg.nextSnap == NULL");

  f = cg.frameInterpolation;

  // this will linearize a sine or parabolic curve, but it is important
  // to not extrapolate player positions if more recent data is available
  BG_EvaluateTrajectory(&cent->currentState.pos, cg.snap->serverTime, current);
  BG_EvaluateTrajectory(&cent->nextState.pos, cg.nextSnap->serverTime, next);

  cent->lerpOrigin[0] = current[0] + f * (next[0] - current[0]);
  cent->lerpOrigin[1] = current[1] + f * (next[1] - current[1]);
  cent->lerpOrigin[2] = current[2] + f * (next[2] - current[2]);

  BG_EvaluateTrajectory(&cent->currentState.apos, cg.snap->serverTime, current);
  BG_EvaluateTrajectory(&cent->nextState.apos, cg.nextSnap->serverTime, next);

  cent->lerpAngles[0] = LerpAngle(current[0], next[0], f);
  cent->lerpAngles[1] = LerpAngle(current[1], next[1], f);
  cent->lerpAngles[2] = LerpAngle(current[2], next[2], f);

}

/*
 ===============
 CG_CalcEntityLerpPositions

 ===============
 */
static void
CG_CalcEntityLerpPositions(centity_t *cent)
{
  // if this player does not want to see extrapolated players
  if (!cg_smoothClients.integer)
  {
    // make sure the clients use TR_INTERPOLATE
    if (cent->currentState.number < MAX_CLIENTS)
    {
      cent->currentState.pos.trType = TR_INTERPOLATE;
      cent->nextState.pos.trType = TR_INTERPOLATE;
    }
  }

  if (cent->interpolate && cent->currentState.pos.trType == TR_INTERPOLATE)
  {
    CG_InterpolateEntityPosition(cent);
    return;
  }

  // first see if we can interpolate between two snaps for
  // linear extrapolated clients
  if (cent->interpolate && cent->currentState.pos.trType == TR_LINEAR_STOP && cent->currentState.number < MAX_CLIENTS)
  {
    CG_InterpolateEntityPosition(cent);
    return;
  }

  // just use the current frame and evaluate as best we can
  BG_EvaluateTrajectory(&cent->currentState.pos, cg.time, cent->lerpOrigin);
  BG_EvaluateTrajectory(&cent->currentState.apos, cg.time, cent->lerpAngles);

  // adjust for riding a mover if it wasn't rolled into the predicted
  // player state
  if (cent != &cg.predictedPlayerEntity)
  {
    CG_AdjustPositionForMover(cent->lerpOrigin, cent->currentState.groundEntityNum, cg.snap->serverTime, cg.time, cent->lerpOrigin);
  }
}

/*
 ===============
 CG_CEntityPVSEnter

 ===============
 */
static void
CG_CEntityPVSEnter(centity_t *cent)
{
  entityState_t *es = &cent->currentState;

  if (cg_debugPVS.integer)
    CG_Printf("Entity %d entered PVS\n", cent->currentState.number);

  switch(es->eType)
  {
    case ET_MISSILE:
      CG_LaunchMissile(cent);
      break;
  }

  //clear any particle systems from previous uses of this centity_t
  cent->muzzlePS = NULL;
  cent->muzzlePsTrigger = qfalse;
  cent->jetPackPS = NULL;
  cent->jetPackState = JPS_OFF;
  cent->buildablePS = NULL;
  cent->entityPS = NULL;
  cent->entityPSMissing = qfalse;

  //make sure that the buildable animations are in a consistent state
  //when a buildable enters the PVS
  cent->buildableAnim = cent->lerpFrame.animationNumber = BANIM_NONE;
  cent->oldBuildableAnim = es->legsAnim;
}

/*
 ===============
 CG_CEntityPVSLeave

 ===============
 */
static void
CG_CEntityPVSLeave(centity_t *cent)
{
  int i;
  entityState_t *es = &cent->currentState;

  if (cg_debugPVS.integer)
    CG_Printf("Entity %d left PVS\n", cent->currentState.number);

  switch(es->eType)
  {
    case ET_LEV2_ZAP_CHAIN:
      for(i = 0;i <= 2;i++)
      {
        if (CG_IsTrailSystemValid(&cent->level2ZapTS[i]))
          CG_DestroyTrailSystem(&cent->level2ZapTS[i]);
      }
      break;
  }
}

/*
 ===============
 CG_AddCEntity

 ===============
 */
static void
CG_AddCEntity(centity_t *cent)
{
  // event-only entities will have been dealt with already
  if (cent->currentState.eType >= ET_EVENTS)
    return;

  // calculate the current origin
  CG_CalcEntityLerpPositions(cent);

  // add automatic effects
  CG_EntityEffects(cent);

  switch(cent->currentState.eType)
  {
    default:
      CG_Error("Bad entity type: %i\n", cent->currentState.eType);
      break;

    case ET_INVISIBLE:
    case ET_PUSH_TRIGGER:
    case ET_TELEPORT_TRIGGER:
      break;

    case ET_GENERAL:
      CG_General(cent);
      break;

    case ET_CORPSE:
      CG_Corpse(cent);
      break;

    case ET_PLAYER:
      CG_Player(cent);
      break;

    case ET_BUILDABLE:
      CG_Buildable(cent);
      break;

    case ET_MISSILE:
      CG_Missile(cent);
      break;

    case ET_MOVER:
      CG_Mover(cent);
      break;

    case ET_BEAM:
      CG_Beam(cent);
      break;

    case ET_PORTAL:
      CG_Portal(cent);
      break;

    case ET_SPEAKER:
      CG_Speaker(cent);
      break;

    case ET_PARTICLE_SYSTEM:
      CG_ParticleSystemEntity(cent);
      break;

    case ET_ANIMMAPOBJ:
      CG_AnimMapObj(cent);
      break;

    case ET_MODELDOOR:
      CG_ModelDoor(cent);
      break;

    case ET_LIGHTFLARE:
      CG_LightFlare(cent);
      break;

    case ET_LEV2_ZAP_CHAIN:
      CG_Lev2ZapChain(cent);
      break;

    case ET_AI_NODE:
      CG_AI_Node(cent);
      break;

    case ET_AI_LINK:
      CG_AI_Link(cent);
      break;
    case ET_LASER:
     // CG_Laser(cent);
    break;
  }
}

/*
 ===============
 CG_AddPacketEntities

 ===============
 */
void
CG_AddPacketEntities(void)
{
  int num;
  centity_t *cent;
  playerState_t *ps;

  // set cg.frameInterpolation
  if (cg.nextSnap)
  {
    int delta;

    delta = (cg.nextSnap->serverTime - cg.snap->serverTime);

    if (delta == 0)
      cg.frameInterpolation = 0;
    else
      cg.frameInterpolation = (float) (cg.time - cg.snap->serverTime) / delta;
  }
  else
  {
    cg.frameInterpolation = 0; // actually, it should never be used, because
    // no entities should be marked as interpolating
  }

  // the auto-rotating items will all have the same axis
  cg.autoAngles[0] = 0;
  cg.autoAngles[1] = (cg.time & 2047) * 360 / 2048.0;
  cg.autoAngles[2] = 0;

  cg.autoAnglesFast[0] = 0;
  cg.autoAnglesFast[1] = (cg.time & 1023) * 360 / 1024.0f;
  cg.autoAnglesFast[2] = 0;

  AnglesToAxis(cg.autoAngles, cg.autoAxis);
  AnglesToAxis(cg.autoAnglesFast, cg.autoAxisFast);

  // generate and add the entity from the playerstate
  ps = &cg.predictedPlayerState;
  BG_PlayerStateToEntityState(ps, &cg.predictedPlayerEntity.currentState, qfalse);
  cg.predictedPlayerEntity.valid = qtrue;
  CG_AddCEntity(&cg.predictedPlayerEntity);

  // lerp the non-predicted value for lightning gun origins
  CG_CalcEntityLerpPositions(&cg_entities[cg.snap->ps.clientNum]);

  // scanner
  CG_UpdateEntityPositions();

  for(num = 0;num < MAX_GENTITIES;num++)
    cg_entities[num].valid = qfalse;

  // add each entity sent over by the server
  for(num = 0;num < cg.snap->numEntities;num++)
  {
    cent = &cg_entities[cg.snap->entities[num].number];
    cent->valid = qtrue;
  }

  for(num = 0;num < MAX_GENTITIES;num++)
  {
    cent = &cg_entities[num];

    if (cent->valid && !cent->oldValid)
      CG_CEntityPVSEnter(cent);
    else if (!cent->valid && cent->oldValid)
      CG_CEntityPVSLeave(cent);

    cent->oldValid = cent->valid;
  }

  // add each entity sent over by the server
  for(num = 0;num < cg.snap->numEntities;num++)
  {
    cent = &cg_entities[cg.snap->entities[num].number];
    CG_AddCEntity(cent);
  }

  //make an attempt at drawing bounding boxes of selected entity types
  if (cg_drawBBOX.integer)
  {
    for(num = 0;num < cg.snap->numEntities;num++)
    {
      float x, zd, zu;
      vec3_t mins, maxs;
      entityState_t *es;

      cent = &cg_entities[cg.snap->entities[num].number];
      es = &cent->currentState;

      switch(es->eType)
      {
        case ET_BUILDABLE:
        case ET_MISSILE:
        case ET_CORPSE:
          x = (es->solid & 255);
          zd = ((es->solid >> 8) & 255);
          zu = ((es->solid >> 16) & 255) - 32;

          mins[0] = mins[1] = -x;
          maxs[0] = maxs[1] = x;
          mins[2] = -zd;
          maxs[2] = zu;

          CG_DrawBoundingBox(cent->lerpOrigin, mins, maxs);
          break;

        default:
          break;
      }
    }
  }
}

